

package	org.jdamico.socks.server.impl;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;

import org.jdamico.socks.server.commons.Configs;
import org.jdamico.socks.server.commons.DebugLog;
import org.jdamico.socks.server.commons.NetworkUtils;
import org.jdamico.socks.server.commons.Utils;

public class Socks4Impl implements SocksCommonInterface {
	public	ProxyHandler	mProxyHandler	= null;

	public	byte	SOCKS_Version = 0; //协议版本
	
	public	byte	mSocksCommand;
	public	byte	mDST_Port[]	= null;
	public	byte	mDST_Addr[]	= null;
	
	public	byte	mUserID[]	= null;
	public	String	mUID = "";
	public	byte	getSuccessCode()	{ return 90; }
	public	byte	getFailCode()		{ return 91; }
	
	protected	InetAddress		mServer	  = null;
	protected	int				mServerPort = 0;
	
	protected	InetAddress		mClientIP	 = null;
	protected	int				mClientPort = 0;
	
	public	InetAddress	getClientAddress()	{ return mClientIP;	}
	public	InetAddress	getServerAddress()	{ return mServer;	}
	public	int			getClientPort()		{ return mClientPort;	}
	public	int			getServerPort()		{ return mServerPort;	}
	

	public	Socks4Impl( ProxyHandler Parent )	{
		mProxyHandler = Parent;
		mDST_Addr = new byte[4];
		mDST_Port = new byte[2];
	}

	public	void	calculateUserID()	{
		String	s = mUID + " ";
		mUserID = s.getBytes();
		mUserID[mUserID.length-1] = 0x00;
	}
	
	public	boolean	calculateAddress()	{
		mServer		= Utils.calcInetAddress( mDST_Addr );
		mServerPort	= Utils.calcPort( mDST_Port[0], mDST_Port[1] );
		mClientIP		= mProxyHandler.mClientSocket.getInetAddress();
		mClientPort	= mProxyHandler.mClientSocket.getPort();
		
		return ( (mServer != null) && (mServerPort >= 0) );
	}							
	
	protected	byte getByte()
	{
		byte b;
		try	{
			b = mProxyHandler.readByteFromClient();
		}
		catch( Exception e )	{
			b = 0;
		}
		return	b;
	}

	public	void	authenticate( byte SOCKS_Ver )
		throws	Exception	{
		SOCKS_Version = SOCKS_Ver;
	}
	

	public void	checkClientCommand() throws Exception {
		
		mSocksCommand	= getByte();
		mDST_Port[0]	= getByte();
		mDST_Port[1]	= getByte();
		for( int i=0; i<4; i++ )	{
			mDST_Addr[i] = getByte();
		}
		
		byte	b;
		while( (b=getByte()) != 0x00 )	{
			mUID += (char)b;
		}
		calculateUserID();
		
		
		if( (mSocksCommand < Configs.SC_CONNECT) || (mSocksCommand > Configs.SC_BIND) )	{
			refuseCommand( (byte)91 );
			throw new Exception( "Socks 4 - Unsupported Command : "+Utils.getCommandName( mSocksCommand ) );
		}
		if( !calculateAddress() )	{  // Gets the IP Address 
			refuseCommand( (byte)92 );	// Host Not Exists...
			throw new Exception( "Socks 4 - Unknown Host/IP address '"+mServer.toString() );
		}
		DebugLog.getInstance().println( "Accepted SOCKS 4 Command: \""+ Utils.getCommandName( mSocksCommand )+"\"" );
	}  
	
	public	void	replyCommand( byte ReplyCode )
	{
		DebugLog.getInstance().println( "Socks 4 reply: \""+Utils.getReplyName( ReplyCode)+"\"" );
		byte[] REPLY = new byte[8];
		REPLY[0]= 0;
		REPLY[1]= ReplyCode;
		REPLY[2]= mDST_Port[0];
		REPLY[3]= mDST_Port[1];
		
		REPLY[4]= mDST_Addr[0];
		REPLY[5]= mDST_Addr[1];
		REPLY[6]= mDST_Addr[2];
		REPLY[7]= mDST_Addr[3];
		mProxyHandler.sendToClient( REPLY );
	} 
			
	protected	void	refuseCommand( byte errorCode )	{
		DebugLog.getInstance().println( "Socks 4 - Refuse Command: \""+Utils.getReplyName(errorCode)+"\"" );
		replyCommand( errorCode );
	}	

	public void	connect() throws Exception {
		DebugLog.getInstance().println( "Connecting..." );
		try	{
			mProxyHandler.connectToServer( mServer.getHostAddress(), mServerPort );
		}
		catch( IOException e )	{
			refuseCommand( getFailCode() ); // Connection Refused
			throw new Exception("Socks 4 - Can't connect to " +
			Utils.getSocketInfo( mProxyHandler.mServerSocket ) );
		}
		DebugLog.getInstance().println( "Connected to "+Utils.getSocketInfo( mProxyHandler.mServerSocket ) );
		replyCommand( getSuccessCode() );
	}	
	
	public	void	bindReply( byte replyCode, InetAddress localhostAddress, int port ) throws	IOException {
		byte	IP[] = {0,0,0,0};
		DebugLog.getInstance().println( "Reply to Client : \""+Utils.getReplyName( replyCode )+"\"" );
		
		byte[] REPLY = new byte[8];
		if( localhostAddress != null ) {
			IP = localhostAddress.getAddress();
		}
						
		REPLY[0]= 0;
		REPLY[1]= replyCode;
		REPLY[2]= (byte)((port & 0xFF00) >> 8);
		REPLY[3]= (byte) (port & 0x00FF);
		REPLY[4]= IP[0];
		REPLY[5]= IP[1];
		REPLY[6]= IP[2];
		REPLY[7]= IP[3];
			
		if( mProxyHandler.isActive() )	{
			mProxyHandler.sendToClient( REPLY );
		}
		else	{
			DebugLog.getInstance().println( "Closed BIND Client Connection" );
		}
	} 
	
	
	public void	bind()	throws IOException
	{
		ServerSocket	ssock	= null;
		InetAddress		localInetAddress	= null;
		int				localPort	= 0;
		
		DebugLog.getInstance().println( "Binding..." );
		Map<String, Object> localInetIdent = NetworkUtils.getLocalInetIdent();
		localInetAddress = InetAddress.getByName((String) localInetIdent.get("ip"));
		DebugLog.getInstance().println( "Local IP : " + localInetAddress.toString() );
		
		try	{	
			ssock = new ServerSocket( 0 );
			ssock.setSoTimeout( Configs.DEFAULT_PROXY_TIMEOUT );
			localPort	= ssock.getLocalPort();
		}
		catch( IOException e )	{  // MyIP == null
			DebugLog.getInstance().println( "Error in BIND() - Can't BIND at any Port" );
			bindReply( (byte)92, localInetAddress,localPort );
			ssock.close();
			return;
		}

		DebugLog.getInstance().println( "BIND at : <"+localInetAddress.toString()+":"+localInetAddress+">" );
		bindReply( (byte)90, localInetAddress, localPort );
									 
		Socket	socket = null;

		while( socket == null )
		{
			if( mProxyHandler.checkClientData() >= 0 ) {
				DebugLog.getInstance().println( "BIND - Client connection closed" );
				ssock.close();
				return;
			}
			try {
				socket = ssock.accept();
				socket.setSoTimeout( Configs.DEFAULT_PROXY_TIMEOUT );
			}
			catch( InterruptedIOException e ) {
				socket.close();
			}
			Thread.yield();
		}
		
		mServer	= socket.getInetAddress();
		mServerPort	= socket.getPort();
		bindReply( (byte)90,	socket.getInetAddress(), socket.getPort() );
		mProxyHandler.mServerSocket = socket;
		mProxyHandler.prepareServer();
		DebugLog.getInstance().println( "BIND Connection from "+Utils.getSocketInfo( mProxyHandler.mServerSocket ) );
		ssock.close();
		
	}

	public	void	udp() throws IOException
	{
		DebugLog.getInstance().println( "Error - Socks 4 don't support UDP Association!" );
		DebugLog.getInstance().println( "Check your Software please..." );
		refuseCommand( (byte)91 );	
	}
}
